package edu.jit.nsi.iot_ms.serviceimpl.session;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import edu.jit.nsi.iot_ms.config.SensorCmd;
import edu.jit.nsi.iot_ms.config.SensorValue;
import edu.jit.nsi.iot_ms.domain.*;
import edu.jit.nsi.iot_ms.mapper.EnvirDataDAO;
import edu.jit.nsi.iot_ms.mapper.EquipmentActionDAO;
import edu.jit.nsi.iot_ms.serviceimpl.custom.EquipServiceImpl;
import edu.jit.nsi.iot_ms.serviceimpl.custom.SensorServiceImpl;
import edu.jit.nsi.iot_ms.serviceimpl.custom.TerminalServiceImpl;
import edu.jit.nsi.iot_ms.transport.ReportData;
import edu.jit.nsi.iot_ms.transport.tcp.EquipNewRsp;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.mina.core.session.IoSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@Service
public class SessionManager {

//    @Value("${jit_embed.rptperiod}")
//    int mina_period;
//
//    @Value("${lora.seeed_rptperiod}")
//    int seeed_period;
//
//    @Value("${lora.easycomm_rptperiod}")
//    int easycomm_period;
//
//    @Value("${ep400.rptperiod}")
//    int ep400_period;

    @Autowired
    TerminalServiceImpl termService;

    @Autowired
    SensorServiceImpl senService;

    @Autowired
    EquipServiceImpl equipService;

    @Autowired
    EnvirDataDAO envdao;

    @Autowired
    EquipmentActionDAO equipmentActionDAO;

    //mina lora nb通用Session Map
    Map<Integer, TermSession> termMap = new HashMap<Integer, TermSession>();
    Map<Integer, SensorParamSession> paramMap = new HashMap<>();
    Map<Integer, EquipSession> equipMap = new HashMap<Integer, EquipSession>();

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * @Description 新增一条 session
     **/
    public synchronized void putOneSess(int termType, int termid, IoSession session, String deveui) {
        //增加term session
        TermSession termSession;
        switch (termType){
            case 1:
                termSession = new TermSession(termid, termType, session);
                break;
            case 2:
            case 3:
            case 4:
            case 5:
                termSession = new TermSession(termid, termType, deveui);
                break;
            default:
                return;
        }
//        if (termType == 1)
//            termSession = new TermSession(termid, termType, session);
//        else if (termType == 2)
//            termSession = new TermSession(termid, termType, deveui);
//        else if (termType == 3)
//            termSession = new TermSession(termid, termType, deveui);
//        else if (termType == 4)
//            termSession = new TermSession(termid, termType, deveui);
//        else
//            return;
        termMap.put(termid, termSession);
        //增加sensor jit.edu.nsi.config session
        List<SensorPhyDO> sensorCfgList = senService.termPhySensors(termid);
        for (SensorPhyDO sencfg : sensorCfgList) {
            if (sencfg.getProduct().toLowerCase().contains("relay") || sencfg.getProduct().toLowerCase().contains("dma"))
                continue;
            List<SensorParamDO> snparams = senService.sensorParams(sencfg.getId());
            for (SensorParamDO param : snparams) {
                termSession.getSensorparams().add(param.getId());
                paramMap.put(param.getId(), new SensorParamSession(sencfg, param));
            }
        }
        //增加equipment jit.edu.nsi.config session
        List<EquipDO> equipList = equipService.equipsInTerm(termid);
        for (EquipDO equipDO : equipList) {
            termSession.getEquiplst().add(equipDO.getId());
            equipMap.put(equipDO.getId(), new EquipSession(equipDO));
        }
    }

    /**
     * @Description 保存AG21终端物理信息
     **/
    public void ag21TermPhyInfo(int termid, List<SensorCmd> sensors){
        for(SensorCmd scmd: sensors){
            int reg = (Integer)scmd.getReg().get(0);
            int len = scmd.getLen();
            String type = scmd.getType();
            List<SensorValue> rspvalst = scmd.getRspvalue();

            for(Integer ad: (List<Integer>)scmd.getAddr()){
                termMap.get(termid).addPhysnrlst(new PhySnrInfo(ad, type, reg, len, rspvalst));
            }

        }
    }

    private void updateSensor(SensorParamSession paramSession, ReportData report, Date now) {
        //覆盖或新增
        paramSession.setValue(report.getValue());
        paramSession.setActive(true);
        paramSession.setTime(now);
    }

    private void updateEquipReport(EquipSession eqSession, ReportData report, int ctlmode, Date now) {
        int road = eqSession.getRoad();
        int value = (int) report.getValue();
        short mask = (short) (1 << (road - 1) & 0xffff);
        byte cur_onoff = (byte) ((value & mask) >> (road - 1)); //0:关闭  或 1:打开
        if (eqSession.getStatus() != cur_onoff) {
            equipmentActionDAO.insert(new EquipActionDO(eqSession.getId(), cur_onoff, (byte) ctlmode, new Date()));
        }
        eqSession.setStatus(cur_onoff);
        eqSession.setActive(true);
        eqSession.setTime(now);
    }

    public  void updateEquipCtl(int termid, int addr, int road, int onoff, int ctlmode) {
        Date now = new Date();

        EquipDO equip = equipService.getEquipbyTermAddr(termid, addr, road);
        if(equip==null){
            log.error("the eqip termid{}, addr{}, road{} not exist in db!",termid, addr, road);
            return;
        }
        EquipSession eqSession = equipMap.get(equip.getId());
        if(eqSession == null){
            log.error("the eqid{} not exist in session!", equip.getId());
            return;
        }
        if (eqSession.getStatus() != onoff) {
            equipmentActionDAO.insert(new EquipActionDO(eqSession.getId(), (byte) onoff, (byte) ctlmode, now));
        }
        eqSession.setStatus(onoff);
        eqSession.setActive(true);
        eqSession.setTime(now);
    }

    //接到上报的消息后更新Sensor Session中的值
    public void updateSessByReport(int termid, List<ReportData> reports, int ctlmode){
        Date now = new Date();
        TermSession termsess = termMap.get(termid);
        termsess.setTime(now);
        List<Integer> senCfgList = termsess.getSensorparams();
        List<Integer> equipList = termsess.getEquiplst();
        for (ReportData report: reports) {
            if(!report.getType().toLowerCase().contains("relay") && !report.getType().toLowerCase().contains("dma")){
                //处理上报的传感器信息
                for (int sid: senCfgList) {
                    SensorParamSession paramSession = paramMap.get(sid);
                    if(paramSession==null){
                        log.error("the sensorid{} not exist in session!", sid);
                    }
                    else if(paramSession.getAddr() == report.getAddr() && paramSession.getReg() == report.getReg()){
                        updateSensor(paramSession, report, now);
                    }
                }
            }else {
                //处理上报的继电器信息
                for (int eqid : equipList) {
                    EquipSession eqSession = equipMap.get(eqid);
                    if(eqSession==null)
                    {
                        log.error("the eqid{} not exist in session!", eqid);
                    }
                    else if (eqSession.getAddr() == report.getAddr()) {
                        updateEquipReport(eqSession, report, ctlmode, now);
                    }
                }
            }
        }
    }

    //根据传感器参数id获取环境参数
    public SensorParamSession getParamData(int spid){
        SensorParamSession sensess = paramMap.get(spid);
        if(sensess==null){
            return null;
        }

//        int interval_min = (int)(new Date().getTime()-sensess.getTime().getTime())/1000/60;
        int reportperoid = 0;
        //获取Sensor对应的Term,找到term的更新时间
        TermSession termSession = termMap.get(sensess.getTermid());
        if(termSession==null){
            log.error("the termid{} match sensorid{} not exist!",sensess.getTermid(), spid);
            return null;
        }
        reportperoid = getTermPeriod(termSession.getTermid());

        //2倍的上报周期
//        if(interval_min > reportperoid*2){
//            return null;
//        }

        return sensess;
    }

    //根据ID获取设备的状态
    public EquipNewRsp getEquipStatus(int equipid){
        EquipSession equipSession = equipMap.get(equipid);
        if(equipSession==null){
            log.error("equipid={} in equipmap(size={}) not exist, maybe not recv report msg!", equipid, equipMap.size());
            return null;
        }
        int interval_sec = (int)(new Date().getTime()-equipSession.getTime().getTime())/1000;
        //2倍的上报周期
        if(interval_sec > 2*getTermPeriod(equipSession.getTermid())){
            return null;
        }
        return new EquipNewRsp(equipid, equipSession.getDefname(), equipSession.getType(), equipSession.getStatus(),null);
    }

//    //根据ID更新设备的状态
//    public void updateEquStaByCtrl(int equipid, byte onoff, Date date){
//        EquipSession equipSession = equipMap.get(equipid);
//        equipSession.setStatus(onoff);
//        equipSession.setTime(new Date());
//        equipmentActionDAO.insert(new EquipActionDO(equipid, onoff, (byte) 0, date));
//    }

    //根据termid/addr/reg获取paramid
    public int getParmid(int termid, int addr, int reg){
        Iterator<SensorParamSession> paramitor = paramMap.values().iterator();
        while(paramitor.hasNext()){
            SensorParamSession paramSession = paramitor.next();
            if(paramSession.getTermid() == termid
                && paramSession.getAddr() == addr
                && paramSession.getReg() == reg){
                return paramSession.getParamid();
            }
        }
        return 0;
    }

    public boolean isTermExist(int termid){
        return termMap.containsKey(termid);
    }

    public int getTermIdByEUI(String eui){
        Iterator<TermSession> termitor = termMap.values().iterator();
        while (termitor.hasNext()){
            TermSession term = termitor.next();
            if(eui.equals(term.getDeveui()))
                return term.getTermid();
        }
        return 0;
    }

    /**
     * @Description AG21终端注册响应
     **/
    public boolean isAG21PhyInfo(int tid){
        TermSession tsession = termMap.get(tid);
        if(tsession!=null && tsession.getPhysnrlst()!=null && tsession.getPhysnrlst().size()>0)
            return true;
        else
            return false;
    }

    /**
     * @Description AG21终端注册响应
     **/
    public PhySnrInfo getTermPhyInfo(int tid, int addr){
        TermSession tsession = termMap.get(tid);
        if(tsession!=null && tsession.getPhysnrlst()!=null){
            for(PhySnrInfo phy:tsession.getPhysnrlst()){
                if(phy.getAddr()==addr)
                    return phy;
            }
        }
        return null;
    }

    /**
     * 周期扫描终端、传感器、控制设备Map缓存
     */
    @Scheduled(fixedDelay = 300*1000)
    private synchronized void periodTimeScan() {
//        List<Integer> outTermLst = new ArrayList<>();
        Iterator<TermSession> iter= termMap.values().iterator();
        while(iter.hasNext()){
            TermSession termSession = iter.next();
            if(!termOLChk(termSession)) { //检查是否在线
                iter.remove();
//                outTermLst.add(termSession.getTermid());
            }
        }
//        List<Integer> outTermLst= new ArrayList<>();
//        for (TermSession termSession : termMap.values()) {
//            if(!termOLChk(termSession)) { //检查是否在线
////                outTermLst.add(termSession.getTermid());
//                termMap.remove(termSession.getTermid());
//            }
//        }

//        for(int tid:outTermLst)
//            termMap.remove(tid);

    }

    /**
     * 检测终端、传感器参数、设备的在线(超时)处理
     */
    private boolean termOLChk(TermSession ts){
        int per_sec = getTermPeriod(ts.getTermid())*1000;
        long now = System.currentTimeMillis();
        //等待iot终端响应超时，需要删除map中的记录ts
        if (now - ts.getTime().getTime() > per_sec*3) {
            log.error("termid={} not recv report, last report is {}, report_period={}",  ts.getTermid(), sdf.format(ts.getTime()),per_sec);
            termLogOut(ts.getTermid());
            return false;
        }else{
            //单独检查每个环境参数是否超时，设置状态false
            for(int pid:ts.getSensorparams()){
                SensorParamSession sp = paramMap.get(pid);
                if(now - sp.getTime().getTime() > per_sec*3){
                    log.error("snpid={} in termid={} not recv report, last report is {}", pid, ts.getTermid(), sdf.format(sp.getTime()));
                    sp.setActive(false);
                }
            }
            //单独检查每个设备状态是否超时，设置状态false
            for(int eid:ts.getEquiplst()){
                EquipSession es = equipMap.get(eid);
                if(now - es.getTime().getTime() > per_sec*3){
                    log.error("eqpid={} in termid={} not recv report, last report is {}", eid, ts.getTermid(), sdf.format(es.getTime()));
                    es.setActive(false);
                }
            }
            return true;
        }
    }

    /**
     * 获取终端的上报时间间隔
     */
    private int getTermPeriod(int tid){
        return termService.getTermDataCycle(tid);
//        return termService.getTermById(ts.getTermid()).getDatacycle();
//        int reportperoid = seeed_period;
//        if(ts.getTermtype() == 1){
//            reportperoid=mina_period;
//        }else if(ts.getTermtype() == 2){
//            reportperoid=seeed_period;
//        }else if(ts.getTermtype() == 3){
//            reportperoid=ep400_period;
//        }else if(ts.getTermtype() == 4){
//            reportperoid=easycomm_period;
//        }
//        return reportperoid;
    }

    /**
     * 获取最后一条数据上报的时间
     * @param termid  终端id
     */
    private String getTermRpTime(int termid){
        List<EnvirDataDO> envlist= envdao.selectList(new EntityWrapper<EnvirDataDO>()
                .eq("termid", termid).orderBy("id", false).last("LIMIT 1"));
        if(envlist!=null&&!envlist.isEmpty()){
            return sdf.format(envlist.get(0).getTime());
        }else{
            return null;
        }
    }

    public IoSession getTermIoSession(int termid){
        return termMap.get(termid).getIosession();
    }

    /**
     * 终端主动下线处理
     */
    public synchronized void termLogOut(int termid){
        TermSession ts = termMap.get(termid);
        if(ts==null){
            log.error("termid={} logout, but not exist in session.", termid);
            return;
        }
        //单独检查每个环境参数是否超时
        for(int pid:ts.getSensorparams()){
            paramMap.remove(pid);
        }
        //单独检查每个设备状态是否超时
        for(int eid:ts.getEquiplst()){
            equipMap.remove(eid);
        }

        IoSession session = ts.getIosession();
        if(session!=null){
            session.closeNow();
        }

        termMap.remove(termid);
    }

    /**
     * 网络原因引起的终端隐式下线
     */
    public void connClose(IoSession iosession){
        for(TermSession item : termMap.values()) {
            if(item.getIosession()!=null && item.getIosession().getRemoteAddress().equals(iosession.getRemoteAddress())){
                log.info("删除Terminal{}的会话Session!", item.getTermid());
//                termMap.remove(item.getTermid());
                termLogOut(item.getTermid());
                return;
            }
        }
    }

    /**
     * 获取终端的状态
     */
    public List<TermSta> getTermSta(String usrname, boolean snep){
        List<TermSta> tslist = new ArrayList<>();
        List<TermDO> terms= null;
        if(usrname!=null) {
            terms = termService.listUsrTerm(usrname);
        }else{
            terms = termService.listAllTerm();
        }
        for(TermDO term:terms){
            TermSta tmsta = new TermSta(term);
            int tid = term.getId();
            //检查是否在线
            if(termMap.containsKey(tid)){
                TermSession  ts =  termMap.get(tid);
                tmsta.setStatus(true);
                tmsta.setIntime(sdf.format(ts.getIntime()));
                if(snep){
                    tmsta.setPhyList(getSensorSta(tid));
                }
            }else{
                tmsta.setStatus(false);
                tmsta.setOuttime(getTermRpTime(tid));
            }
            tslist.add(tmsta);
        }

        return tslist;
    }

    /**
     * 获取终端下面接入传感器的状态
     */
    private List<SnPhySta> getSensorSta(int tid){
        List<SnPhySta> stalist = new ArrayList<>();
        List<SensorPhyDO> phylist = senService.termPhySensors(tid);
        for(SensorPhyDO phy:phylist){
            SnPhySta snsta = new SnPhySta(phy.getId(), phy.getName());
            if(!phy.getProduct().toLowerCase().contains("relay")){
                //处理传感器
                for(SensorParamSession sp:paramMap.values()){
                    if(phy.getId() == sp.getPhyid()){ //终端下传感器保存在map，离线也不会删除
                        snsta.setPhystatus(sp.isActive());
                        snsta.setLstime(sdf.format(sp.getTime()));
                        break;
                    }
                }
            }else{
                //处理继电器
                for(EquipSession ep : equipMap.values()){
                    if(phy.getAddr() == ep.getAddr()){
                        snsta.setPhystatus(ep.isActive());
                        snsta.setLstime(sdf.format(ep.getTime()));
                    }
                }
            }
            stalist.add(snsta);
        }
        return stalist;
    }


    @Data
    public static class TermSta{
        public int termid;
        public String termname;
        public String usrname;
        public boolean status;
        public String intime;
        public String outtime;
        public List<SnPhySta> phyList;

        public TermSta(TermDO tdo){
            termid = tdo.getId();
            termname = tdo.getName();
            usrname = tdo.getUsername();
            status = false;
        }
    }

    @Data
    public static class SnPhySta {
        public int phyid;
        public String phyname;
        public boolean phystatus;
        public String lstime;

        public SnPhySta(int snid, String name){
            phyid = snid;
            phyname = name;
            phystatus=false;
        }
    }

}
